﻿using DynORM.Enums;
using DynORM.ER.Mappers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace DynORM.BLLMapper.Helpers
{
    public class OptimisticLockChecker
    {
        public static bool IsBLLObjTypeUsesOptimisticLock(Type bllObjType)
        {
            List<MemberInfo> mis = new List<MemberInfo>();

            IEnumerable<FieldInfo> fieldInfos = bllObjType.GetFields(BindingFlags.Public | BindingFlags.Instance);
            fieldInfos = fieldInfos.Where(fi => fi.GetCustomAttributes(typeof(OptimisticLockAttribute), true).Count() > 0);
            foreach (FieldInfo fi in fieldInfos)
                mis.Add(fi);

            IEnumerable<PropertyInfo> propertyInfos = bllObjType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            propertyInfos = propertyInfos.Where(pi => pi.GetCustomAttributes(typeof(OptimisticLockAttribute), true).Count() > 0);
            foreach (PropertyInfo pi in propertyInfos)
                if (pi.CanRead && pi.CanWrite)
                    mis.Add(pi);
            if (mis.Count != 1) //object's type does not support locking logic
                return false;
            else
                return true;
        }

        public static MemberInfo GetBLLObjTypeOptimisticLockMemberInfo(Type bllObjType)
        {
            List<MemberInfo> result = new List<MemberInfo>();

            IEnumerable<FieldInfo> fieldInfos = bllObjType.GetFields(BindingFlags.Public | BindingFlags.Instance);
            fieldInfos = fieldInfos.Where(fi => fi.GetCustomAttributes(typeof(OptimisticLockAttribute), true).Count() > 0);
            foreach (FieldInfo fi in fieldInfos)
                result.Add(fi);

            IEnumerable<PropertyInfo> propertyInfos = bllObjType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            propertyInfos = propertyInfos.Where(pi => pi.GetCustomAttributes(typeof(OptimisticLockAttribute), true).Count() > 0);
            foreach (PropertyInfo pi in propertyInfos)
                if (pi.CanRead && pi.CanWrite)
                    result.Add(pi);
            if (result.Count != 1) //object's type does not support locking logic
                throw new Exception("Use optimistic lock infrastructure noly for object that declared with lock members. Invoke GetBLLObjTypeOptimisticLockMemberInfo() only if IsBLLObjTypeUsesOptimisticLock() returned true.");
            return result.First<MemberInfo>();
        }

        public static int GetVersion(object bllObj)
        {
            int result = 0;
            MemberInfo mi = GetBLLObjTypeOptimisticLockMemberInfo(bllObj.GetType());
            if (mi is FieldInfo)
            {
                FieldInfo fi = mi as FieldInfo;
                if (fi.FieldType != typeof(Int32))
                    throw new Exception("Class member attributed with OptimisticLockAttribute should typed as Int32.");
                result = (int)fi.GetValue(bllObj);
            }
            else if (mi is PropertyInfo)
            {
                PropertyInfo pi = mi as PropertyInfo;
                if (pi.PropertyType != typeof(Int32))
                    throw new Exception("Class member attributed with OptimisticLockAttribute should typed as Int32.");
                result = (int)pi.GetValue(bllObj, null);
            }
            else
                throw new Exception("Only fields or properties can be attributed with OptimisticLockAttribute");

            return result;
        }

        public static bool CheckVersion(object bllObj, string connectionString, DBMSTypes dbmsType)
        {
            IEnumerable<KeyValuePair<int, MemberInfo>> pkmis = TypeAndRowDescriptorAux.GetBLLObjTypePrimaryKeyMemberInfos(bllObj.GetType());
            object[] bllObjIDs = new object[pkmis.Count<KeyValuePair<int, MemberInfo>>()];
            foreach (KeyValuePair<int, MemberInfo> kv in pkmis)
                if (kv.Value is FieldInfo)
                    bllObjIDs[kv.Key] = ((FieldInfo)kv.Value).GetValue(bllObj);
                else if (kv.Value is PropertyInfo)
                    bllObjIDs[kv.Key] = ((PropertyInfo)kv.Value).GetValue(bllObj, null);
                else
                    throw new Exception("Only fields or properties can be attributed with PrimaryAttribute");

            //object storedBLLObj = ReadLazy(bllObj.GetType(), bllObjIDs);
            object storedBLLObj = Entity.Read(bllObj.GetType(), connectionString, dbmsType, bllObjIDs);
            if (storedBLLObj == null) //new object
                return true;

            if (GetVersion(bllObj) < GetVersion(storedBLLObj))
                return false;
            else
                return true;
        }

        public static void IncrementVersion(object bllObj)
        {
            int newVer = GetVersion(bllObj);
            newVer++;
            MemberInfo mi = GetBLLObjTypeOptimisticLockMemberInfo(bllObj.GetType());
            if (mi is FieldInfo)
            {
                FieldInfo fi = mi as FieldInfo;
                if (fi.FieldType != typeof(Int32))
                    throw new Exception("Class member attributed with OptimisticLockAttribute should typed as Int32.");
                fi.SetValue(bllObj, newVer);
            }
            else if (mi is PropertyInfo)
            {
                PropertyInfo pi = mi as PropertyInfo;
                if (pi.PropertyType != typeof(Int32))
                    throw new Exception("Class member attributed with OptimisticLockAttribute should typed as Int32.");
                pi.SetValue(bllObj, newVer, null);
            }
            else
                throw new Exception("Only fields or properties can be attributed with OptimisticLockAttribute");
        }

        public static void CheckOptimisticLock(object bllObj, string connectionString, DBMSTypes dbmsType)
        {
            if (!IsBLLObjTypeUsesOptimisticLock(bllObj.GetType()))
                return;
            IncrementVersion(bllObj);
            if (!CheckVersion(bllObj, connectionString, dbmsType))
                throw new Exception("Storing data is obsolete. Reretrieve data from the server and try to store updates again.");
        }
    }
}
